#include "stdafx.h"
#include "CppUnitTest.h"

using namespace Microsoft::VisualStudio::CppUnitTestFramework;

#include <vex/core/as_intrusive.h>
#include <vex/functional/implementation/default_value.h>
#include <vex/functional/implementation/default_value_reducer.h>
#include <vex/functional/contract/multicast_delegate.h>
#include <vex/functional/contract/invokable_signal.h>
#include <vex/functional/make_raw_multicast_delegate.h>
#include <vex/functional/concept/reducer_concept.h>

namespace tests { namespace tests_functional_mc_reducer {		

    namespace vf = vex::functional;
    namespace vi = vex::iterable;

   
    TEST_CLASS(test_class)
    {
        BEGIN_TEST_CLASS_ATTRIBUTE()
            TEST_CLASS_ATTRIBUTE(L"Module", L"tests.functional.mc.reducer")
        END_TEST_CLASS_ATTRIBUTE();
    public:
        static const int c_42 = 42;
        static int s_42;
        
        static int const & returns_const_ref()
        {
            return c_42;
        }

        static int const * returns_const_ptr()
        {
            return &c_42;
        }

        static int& returns_ref()
        {
            return s_42;
        }

        static int* returns_ptr()
        {
            return &s_42;
        }

        template<class T> static T* returns_null_ptr()
        {
            return nullptr;
        }

        template<class T> static T returns_temporary()
        {
            return T();
        }

        template<class T> static T&& returns_rvalue()
        {
            return T();
        }

        TEST_METHOD(result_of_default_value_yields_correct_result_value_type)
        {
            namespace vfi = vf::implementation;
            {
                typedef vfi::result_value<void> exp_t;
                typedef decltype(vf::default_value()) test_t;
                Assert::IsTrue(std::is_same<exp_t, test_t>::value, wlog_message() 
                    << "expected: " << std::endl 
                    << typeid(exp_t).name() << std::endl
                    << "got: " << std::endl
                    << typeid(test_t).name() | as_string);
            }

            {
                typedef vfi::result_value<int> exp_t;
                typedef decltype(vf::default_value(42)) test_t;
                Assert::IsTrue(std::is_same<exp_t, test_t>::value, wlog_message() 
                    << "expected: " << std::endl 
                    << typeid(exp_t).name() << std::endl
                    << "got: " << std::endl
                    << typeid(test_t).name() | as_string);
            }

            {
                typedef vfi::result_value<int const &> exp_t;
                typedef decltype(vf::default_value(c_42)) test_t;
                Assert::IsTrue(std::is_same<exp_t, test_t>::value, wlog_message() 
                    << "expected: " << std::endl 
                    << typeid(exp_t).name() << std::endl
                    << "got: " << std::endl
                    << typeid(test_t).name() | as_string);
            }

            {
                typedef vfi::result_value<int const &> exp_t;
                typedef decltype(vf::default_value(returns_const_ref())) test_t;
                Assert::IsTrue(std::is_same<exp_t, test_t>::value, wlog_message() 
                    << "expected: " << std::endl 
                    << typeid(exp_t).name() << std::endl
                    << "got: " << std::endl
                    << typeid(test_t).name() | as_string);
            }

            {
                typedef vfi::result_value<int &> exp_t;
                typedef decltype(vf::default_value(returns_ref())) test_t;
                Assert::IsTrue(std::is_same<exp_t, test_t>::value, wlog_message() 
                    << "expected: " << std::endl 
                    << typeid(exp_t).name() << std::endl
                    << "got: " << std::endl
                    << typeid(test_t).name() | as_string);
            }

            {
                typedef vfi::result_value<char const*> exp_t;
                typedef decltype(vf::default_value("bimbo")) test_t;
                Assert::IsTrue(std::is_same<exp_t, test_t>::value, wlog_message() 
                    << "expected: " << std::endl 
                    << typeid(exp_t).name() << std::endl
                    << "got: " << std::endl
                    << typeid(test_t).name() | as_string);
            }

            {
                typedef vfi::result_value<int const*> exp_t;
                typedef decltype(vf::default_value(returns_const_ptr())) test_t;
                Assert::IsTrue(std::is_same<exp_t, test_t>::value, wlog_message() 
                    << "expected: " << std::endl 
                    << typeid(exp_t).name() << std::endl
                    << "got: " << std::endl
                    << typeid(test_t).name() | as_string);
            }

            {
                typedef vfi::result_value<int*> exp_t;
                typedef decltype(vf::default_value(returns_null_ptr<int>())) test_t;
                Assert::IsTrue(std::is_same<exp_t, test_t>::value, wlog_message() 
                    << "expected: " << std::endl 
                    << typeid(exp_t).name() << std::endl
                    << "got: " << std::endl
                    << typeid(test_t).name() | as_string);
            }

            {
                typedef vfi::result_value<int> exp_t;
                typedef decltype(vf::default_value(returns_temporary<int>())) test_t;
                Assert::IsTrue(std::is_same<exp_t, test_t>::value, wlog_message() 
                    << "expected: " << std::endl 
                    << typeid(exp_t).name() << std::endl
                    << "got: " << std::endl
                    << typeid(test_t).name() | as_string);
            }

            {
                typedef vfi::result_value<int> exp_t;
                typedef decltype(vf::default_value(returns_rvalue<int>())) test_t;
                Assert::IsTrue(std::is_same<exp_t, test_t>::value, wlog_message() 
                    << "expected: " << std::endl 
                    << typeid(exp_t).name() << std::endl
                    << "got: " << std::endl
                    << typeid(test_t).name() | as_string);
            }
        }

        TEST_METHOD(result_of_default_value_can_be_invoked_and_delivers_expected_values)
        {
            VEX_TESTS_SCOPED_MEM_CHECK();
            namespace vfi = vf::implementation;

            {
                auto d = vf::default_value();
                Assert::IsTrue(std::is_same<void, std::result_of<decltype(d)()>::type>::value);
                d();
                d();
                // copy constructible
                typedef std::remove_reference<decltype(d)>::type d_type;
                d_type d_other = d;
                d_other();
                // assignable
                d = d_other;
                d();
            }

            {   
                auto d = vf::default_value(42);
                Assert::IsTrue(std::is_same<int, std::result_of<decltype(d)()>::type>::value);
                Assert::AreEqual(42, d());
                Assert::AreEqual(42, d());
                // copy constructible
                typedef std::remove_reference<decltype(d)>::type d_type;
                d_type d_other = d;
                Assert::AreEqual(d(), d_other());
                // assignable
                d = d_other;
                d();
                // re-assignable from value and from function call
                d = 43;
                Assert::AreEqual(43, d());
                d = ([]() { return 44; })();
                Assert::AreEqual(44, d());
            }


            {           
                auto d = vf::default_value(c_42);
                Assert::IsTrue(std::is_same<int const &, std::result_of<decltype(d)()>::type>::value);
                Assert::AreEqual(42, d());
                Assert::AreEqual(&c_42, std::addressof(d()));
                Assert::AreEqual(42, d());
                Assert::AreEqual(&c_42, std::addressof(d()));
                // copy constructible
                typedef std::remove_reference<decltype(d)>::type d_type;
                d_type d_other = d;
                Assert::AreEqual(d(), d_other());
                Assert::AreEqual(&d(), &d_other());
                // assignable
                d = d_other;
                d();
                // re-assignable from value and from function call
                int c_local_43 = 43;
                d = static_cast<int const &>(c_local_43);
                Assert::AreEqual(43, d());
                Assert::AreEqual(&static_cast<int const &>(c_local_43), &d());
                d = ([&]() -> int const & { return c_local_43; })();
                Assert::AreEqual(43, d());
                Assert::AreEqual(&static_cast<int const &>(c_local_43), &d());
            }

            {           
                auto d = vf::default_value(s_42);
                Assert::IsTrue(std::is_same<int &, std::result_of<decltype(d)()>::type>::value);
                Assert::AreEqual(42, d());
                Assert::AreEqual(&s_42, std::addressof(d()));
                Assert::AreEqual(42, d());
                Assert::AreEqual(&s_42, std::addressof(d()));
            }

            {           
                auto d = vf::default_value(returns_const_ptr());
                Assert::IsTrue(std::is_same<int const *, std::result_of<decltype(d)()>::type>::value);
                Assert::IsNotNull(d());
                Assert::AreEqual(&c_42, d());
                Assert::AreEqual(42, *d());
            }

            {
                auto d = vf::default_value("bimbo");
                Assert::IsTrue(std::is_same<char const *, std::result_of<decltype(d)()>::type>::value);
                Assert::IsNotNull(d());
                Assert::AreEqual("bimbo", d());
                // copy constructible
                typedef std::remove_reference<decltype(d)>::type d_type;
                d_type d_other = d;
                Assert::AreEqual(d(), d_other());
                // assignable
                d = d_other;
                d();
                // re-assignable from value and from function call
                char * const c_local = "bambo";
                d = static_cast<char* const>(c_local);
                Assert::AreEqual(c_local, d());
                Assert::AreEqual(static_cast<void const*>(c_local), static_cast<void const*>(d()));
                d = ([]() -> char const * { return "bambo2"; })();
                Assert::AreEqual("bambo2", d());
            }
        }

        TEST_METHOD(make_default_value_reducer_yields_expected_type_from_signature_and_default_value)
        {
            VEX_TESTS_SCOPED_MEM_CHECK();
            namespace vfi = vf::implementation;

            {
                typedef void (signature_t)();
                typedef decltype(vf::default_reducer()) test_t;
                typedef vfi::default_value_reducer<vf::result<signature_t>::type> exp_t;
                Assert::IsTrue(std::is_same<exp_t, test_t>::value, wlog_message() 
                    << "expected: " << std::endl 
                    << typeid(exp_t).name() << std::endl
                    << "got: " << std::endl
                    << typeid(test_t).name() | as_string);
            }

            {   
                typedef int (signature_t)();
                typedef decltype(vf::default_reducer(42)) test_t;
                typedef vfi::default_value_reducer<vf::result<signature_t>::type> exp_t;
                Assert::IsTrue(std::is_same<exp_t, test_t>::value, wlog_message() 
                    << "expected: " << std::endl 
                    << typeid(exp_t).name() << std::endl
                    << "got: " << std::endl
                    << typeid(test_t).name() | as_string);
            }

            {   
                typedef int const & (signature_t)();
                typedef decltype(vf::default_reducer(c_42)) test_t;
                typedef vfi::default_value_reducer<vf::result<signature_t>::type> exp_t;
                Assert::IsTrue(std::is_same<exp_t, test_t>::value, wlog_message() 
                    << "expected: " << std::endl 
                    << typeid(exp_t).name() << std::endl
                    << "got: " << std::endl
                    << typeid(test_t).name() | as_string);
            }
        }

        TEST_METHOD(default_value_reducer_yields_default_value_when_invoked_with_nullptr)
        {
            VEX_TESTS_SCOPED_MEM_CHECK();
            namespace vfi = vf::implementation;

            {
                typedef void (signature_t)();
                auto r = vf::default_reducer();
                r(nullptr);
            }

            {   
                typedef int (signature_t)();
                auto r = vf::default_reducer(42);
                Assert::AreEqual(42, r(nullptr));
            }

            {   
                typedef int const & (signature_t)();
                auto r = vf::default_reducer(c_42);
                Assert::AreEqual(static_cast<int const&>(c_42), r(nullptr));
                Assert::AreEqual(&c_42, &r(nullptr));
            }

            {   
                typedef int & (signature_t)();
                auto r = vf::default_reducer(s_42);
                Assert::AreEqual(static_cast<int&>(s_42), r(nullptr));
                Assert::AreEqual(&s_42, &r(nullptr));
            }
        }
    };

    int test_class::s_42 = 42;
}}